Skip to content

16 Dockerfile

Docker 镜像原理

思考

  • Docker 镜像本质是什么?
    • 是一个分层文件系统
  • Docker 中一个 centos 镜像为什么只有 200MB,而一个 centos 操作系统的 iso 文件要几个 G?
    • Centos 的 iso 镜像文件包含 bootfs 和 rootfs,而 docker 的 centos 镜像复用操作系统的 bootfs,只有 rootfs 和其他镜像层
  • Docker 中一个 tomcat 镜像为什么有 500MB,而一个 tomcat 安装包只有 70 多 MB?
    • 由于 docker 中镜像是分层的,tomcat 虽然只有 70 多 MB,但他需要依赖于父镜像和基础镜像,所有整个对外暴露的 tomcat 镜像大小 500 多 MB

操作系统组成部分

  1. 进程管理子系统:
    • 任务调度器(Scheduler): 控制在多任务环境下,哪个进程获得 CPU 时间。
    • 进程调度和管理: 管理进程的创建、调度、终止等操作。
    • 进程间通信(IPC): 提供进程之间进行通信的机制,如信号、消息队列、共享内存等。
  2. 内存管理子系统:
    • 虚拟内存管理: 管理进程的虚拟内存空间,包括分页和分段。
    • 物理内存管理: 控制物理内存的分配和释放。
    • 页面置换算法: 当物理内存不足时,选择将哪些页面置换到磁盘。
  3. 文件系统子系统:
    • 文件系统管理: 提供文件和目录的创建、读取、写入和删除等功能。
    • 文件权限和所有权: 控制文件的访问权限和所有者。
    • 磁盘空间管理: 管理文件系统中的磁盘空间,包括文件块的分配和释放。
  4. 网络子系统:
    • TCP/IP 协议栈: 实现网络通信的基础协议。
    • 设备驱动程序: 提供硬件设备与内核之间的接口,允许操作系统与硬件设备进行通信。
    • 网络配置工具: 允许用户配置网络接口、IP 地址、路由表等网络参数。
  5. 设备驱动程序子系统:
    • 设备驱动管理: 管理和加载硬件设备的驱动程序。
    • 硬件抽象层(HAL): 提供一种标准接口,使得内核和设备驱动程序能够独立于底层硬件进行开发。
  6. 用户接口子系统:
    • Shell: 提供用户与内核交互的命令行接口。
    • 图形用户界面(GUI): 提供图形化的用户界面,包括窗口管理器和桌面环境。
  7. 系统调用接口:
    • 系统调用: 提供用户空间程序与内核空间之间的接口,允许用户程序请求操作系统的服务。
    • C 库(glibc): 提供与系统调用交互的标准函数库。
  8. 安全和权限管理子系统:
    • 用户账户和组管理: 管理系统中的用户和用户组。
    • 访问控制: 控制用户对文件和系统资源的访问权限。
    • 安全策略: 实施安全策略,如 SELinux(Security-Enhanced Linux)。

Linux 文件系统

  • 在 Linux 中,bootfsrootfs 是两个关键的文件系统,它们在系统引导过程和系统运行时发挥着重要的作用。
  • 不同的 linux 发行版,bootfs 基本一样,而 rootfs 不同,如 ubuntu,centos 等。
  • 在系统启动时,bootfs 负责引导加载程序和内核的加载,而 rootfs 则负责提供运行时的根文件系统,使得系统能够正常启动和运行。
  • 这两个文件系统的协同工作确保了 Linux 系统的顺利启动和正常运行。

bootfs

  • 位置: 通常位于启动设备(如硬盘、SSD)的引导分区,它可以是一个专门的分区,也可以是 EFI 分区等。
  • 作用: bootfs 包含了引导加载程序(bootloader)和内核映像(kernel image)。引导加载程序负责在计算机启动时加载操作系统内核,而内核映像则是操作系统核心的二进制文件。
  • GRUB(GRand Unified Bootloader): 在许多 Linux 系统中,特别是使用传统 BIOS 的系统,GRUB 是一种常见的引导加载程序。GRUB 负责在启动时加载内核映像,并向内核传递必要的参数,例如内核的位置、启动参数等。
  • UEFI(Unified Extensible Firmware Interface): 对于使用 UEFI 的系统,通常会有一个专用的 EFI 分区,其中包含了引导加载程序文件(通常是 grubx64.efi 等)。UEFI 提供了更现代和灵活的引导过程。

rootfs

  • 位置: rootfs 是一个虚拟文件系统,它在系统运行时被挂载为根文件系统。它可以是内存中的 tmpfs,也可以是硬盘上的实际文件系统,例如 ext4。
  • 作用: rootfs 包含了系统运行时所需的基本文件和目录,包括 /bin/sbin/lib/etc 等。这些文件和目录组成了用户空间的基本结构,支持系统的正常运行。
  • 虚拟文件系统: rootfs 是一个虚拟文件系统,它可能是一个初始的根文件系统,而随着系统的运行,它可以被更换为其他实际的文件系统。这种灵活性允许系统在运行时切换根文件系统,例如在初始化阶段使用 initramfs 后,切换到实际的根文件系统。
  • Initramfs(Initial RAM File System): 在一些系统中,特别是在启动时需要加载特定驱动程序或进行硬件检测的情况下,可能会使用 initramfsinitramfs 是一个临时的文件系统,它会在引导过程中加载到内存中,并在引导完成后被卸载。

镜像原理

dockerfile
# 基础镜像
FROM ubuntu:20.04
# 在基础镜像上进行操作,构建新的镜像层
RUN apt-get update && apt-get install -y nginx

# 上面的 Dockerfile 中,`FROM ubuntu:20.04` 表示基础镜像是 Ubuntu 20.04 镜像
# `RUN apt-get update && apt-get install -y nginx` 表示在基础镜像上运行命令安装 Nginx,形成新的镜像层
# 这个 Dockerfile 就是一个简化的示例,实际上可能包含更多操作和配置

# 容器启动时,在最顶层加载一个读写文件系统作为容器
# 每个容器实例都有自己的顶层文件系统,可以在容器内进行读写操作,而不影响底层的镜像文件系统

Docker 镜像构建原理

  • Docker 镜像是通过特殊的文件系统叠加而成的。

文件系统层次结构

  • 最底端是 bootfs,并使用宿主机的 bootfs
  • 第二层是 root文件系统rootfs),被称为基础镜像(base image)。
  • 往上可以叠加其他的镜像文件,形成分层的结构。

Union File System 技术

  • 使用统一文件系统(Union File System)技术,将不同层次的文件系统整合成一个。
  • 提供了一个统一的视角,用户在使用时只看到一个文件系统,隐藏了底层多层的存在。

镜像的层次关系

  • 一个镜像可以放在另一个镜像的上面。
  • 位于下面的镜像称为父镜像,最底部的镜像称为基础镜像。

容器启动过程

  • 当从一个镜像启动容器时,Docker 会在最顶层加载一个读写文件系统作为容器。

镜像制作

容器转为镜像

  1. 获取正在运行的容器的 ID.

    bash
    docker ps
  2. 保存容器状态为镜像。

    • [container_id] 为容器 ID
    • [image_name] 为新镜像的名字
    • [tag] 为标签
    bash
    docker commit [container_id] [image_name]:[tag]
  3. 查看新创建的镜像。

    bash
    docker images
  4. 推送到 Docker Hub(可选)。

    bash
    docker login
    docker push [image_name]:[tag]
  5. 本地使用(可选)。

    bash
    # 保存
    docker save -o [image_name].tar.gz [image_name]:[tag]
    # 加载
    docker load -i [image_name].tar.gz

dockerfile

  1. 创建 Dockerfile

    • 创建一个文本文件,通常命名为 Dockerfile
    • 这个文件包含了一系列指令,按照顺序描述了构建 Docker 镜像的步骤。
  2. 选择基础镜像

    dockerfile
    FROM image[:tag] [AS name]
    • 在 Dockerfile 中使用 FROM 指令选择一个基础镜像作为构建的起点。常见的选择包括 alpine、debian、ubuntu、centos、node、python、nginx 等。
    • 基础镜像包含了操作系统和一些基本的软件包,例如 FROM ubuntu:20.04 表示基于 Ubuntu 20.04 镜像。
      • scratch 是一个特殊的基础镜像,它实际上是一个空白镜像,不包含任何文件系统内容。因此,使用 scratch 作为基础镜像意味着你从头开始构建你的镜像,而不是基于其他镜像。
      • Alpine Linux 以小巧、高效而著称,它的 基础镜像 只有几兆大小(目前小于 6 MB),同时仍是一个完整的 Linux 发行版。这使得构建和部署容器时能够更快地完成。
    • 有时候,你可能需要自定义基础镜像,以满足特定的需求。可以在 FROM 指令中使用 AS name 为基础镜像命名,然后在后续的指令中引用这个名字。
    • 例如,FROM ubuntu:20.04 AS builder,在后续的指令中可以使用 FROM builder 引用这个自定义的基础镜像。
  3. 指定维护者的信息。

    dockerfile
    MAINTAINER xxx yourname@example.com
    • 指定维护者的信息,但推荐使用 LABEL 代替。
  4. 为镜像添加元数据。

    dockerfile
    LABEL version="1.0" maintainer="yourname@example.com"
    • 用于为镜像添加元数据,通常用于提供关于镜像的信息,如版本、维护者等。
  5. 运行命令和安装软件

    dockerfile
    # 安装应用程序依赖
    RUN apt-get update && apt-get install -y python3
    • 使用 RUN 指令运行命令,在基础镜像上进行操作,例如安装软件、更新软件包等。
    • 例如,RUN apt-get update && apt-get install -y nginx 表示在基础镜像上更新软件包并安装 Nginx。
  6. 复制文件

    dockerfile
    # 复制应用程序文件到容器中
    COPY . /app
    
    # 复制并解压
    ADD archive.tar.gz /app/archive
    • 使用 COPYADD 指令将本地文件或目录复制到镜像中。
    • 例如,COPY ./myapp /app 表示将本地的 myapp 目录复制到镜像的 /app 目录。
  7. 设置环境变量

    dockerfile
    ENV APP_HOME /app
    ENV PORT 8080
    
    # 使用环境变量在后续指令中定义路径
    ENV APP_HOME /app
    WORKDIR $APP_HOME
    COPY . $APP_HOME
    
    # 在容器运行时使用环境变量
    ENV PORT=8080
    EXPOSE $PORT
    CMD ["python3", "-m", "http.server", "$PORT"]
    • 使用 ENV 指令设置环境变量,例如 ENV APP_HOME /app
    • 这可以为后续的指令提供一个可重用的变量。
  8. 设置工作目录

    dockerfile
    # 设置工作目录
    WORKDIR /app
    • 使用 WORKDIR 指令设置工作目录,例如 WORKDIR /app
    • 这会影响后续的指令,使其相对于该目录执行。
  9. 声明容器监听的端口

    dockerfile
    # 暴露应用程序的端口
    EXPOSE 80
    • 使用 EXPOSE 指令声明容器运行时监听的端口,例如 EXPOSE 80
  10. 定义启动命令

    dockerfile
    # 定义容器启动时要执行的默认命令,可以被 Dockerfile 中的任何 CMD 或 docker run 命令覆盖
    CMD ["python3", "app.py"]
    
    # 定义容器启动时要执行的命令,不可被 Dockerfile 中的 CMD 或 docker run 命令覆盖
    ENTRYPOINT ["python3", "app.py"]
    • 使用 CMDENTRYPOINT 指令定义容器启动时要运行的命令。

    • 例如,CMD ["nginx", "-g", "daemon off;"] 表示容器启动时运行 Nginx 以非守护进程方式。

  11. 构建镜像

    bash
    docker build -t myapp:latest .
    • 运行 docker build -t image_name:tag . 命令来构建镜像。
    • image_name 是镜像名,tag 是版本标签,. 表示当前目录。
  12. 推送到 Docker Hub(可选)

    bash
    docker images
    docker login
    docker push myapp:latest
    • 如果你希望分享你的镜像,可以将其推送到 Docker Hub 或其他容器注册表。
    • 使用 docker push your_image_name:tag 命令推送镜像。
  13. 保存到本地(可选)

    bash
    # 保存
    docker save -o myapp.tar.gz myapp:latest
    # 加载
    docker load -i myapp.tar.gz
  14. 运行容器(可选)

    bash
    docker run -p 8080:80 myapp:latest

Dockerfile 概念及作用

  • Dockerfile 是一个文本文件: Dockerfile 是一个文本文件,包含了一系列构建 Docker 镜像的指令。
  • 每一条指令构建一层: 每一条 Dockerfile 中的指令都会构建一层,这些层是基于基础镜像的,最终形成一个完整的新镜像。
  • 基于基础镜像构建新镜像: Dockerfile 指定了构建新镜像所需的基础镜像,并通过一系列指令对其进行定制,形成新的镜像。
  • 为开发人员提供一致的开发环境: Dockerfile 的使用可以为开发人员提供一个一致的开发环境,确保在不同环境中的工作一致性。
  • 为测试人员提供工作环境: 测试人员可以直接使用开发人员构建的镜像,或者通过 Dockerfile 文件构建一个新的镜像,以便开始工作。
  • 在部署时实现应用的无缝移植: 运维人员在部署时可以使用 Dockerfile 构建镜像,实现应用的无缝移植,确保在不同环境中的一致性。

Dockerfile 关键字

关键字作用备注
FROM指定父镜像指定 dockerfile 基于那个 image 构建。
MAINTAINER作者信息用来标明这个 dockerfile 的作者。
LABEL标签用来标明 dockerfile 的标签,可以使用 Label 代替 Maintainer,最终都是在 docker image 基本信息中可以查看。
RUN执行命令执行一段命令,默认是 /bin/sh
格式:RUN command 或者 RUN ["command" , "param1","param2"]
CMD容器启动命令提供启动容器时候的默认命令 和 ENTRYPOINT 配合使用。
格式 CMD command param1 param2 或者 CMD ["command" , "param1","param2"]
ENTRYPOINT入口一般在制作一些执行就关闭的容器中会使用。
COPY复制文件build 的时候复制文件到 image 中。
ADD添加文件build 的时候添加文件到 image 中,不仅仅局限于当前 build 上下文,可以来源于远程服务。
ENV环境变量指定 build 时候的环境变量,可以在启动的容器的时候通过 -e 覆盖。
格式 ENV name=value
ARG构建参数构建参数。只在构建的时候使用的参数。如果有 ENV ,那么 ENV 的相同名字的值始终覆盖 arg 的参数。
VOLUME定义外部可以挂载的数据卷指定 build 的 image 那些目录可以启动的时候挂载到文件系统中。启动容器的时候使用 -v 绑定。
格式 VOLUME ["目录"]
EXPOSE暴露端口定义容器运行的时候监听的端口。启动容器的使用 -p 来绑定暴露端口。
格式:EXPOSE 8080 或者 EXPOSE 8080/udp
WORKDIR工作目录指定容器内部的工作目录。如果没有创建则自动创建。
如果指定 / 使用的是绝对地址。
如果不是 / 开头那么是在上一条 workdir 的路径的相对路径。
USER指定执行用户指定 build 或者启动的时候,用户在 RUN CMD ENTRYPONT 执行的时候的用户。
HEALTHCHECK健康检查指定监测当前容器的健康监测的命令,基本上没用。因为很多时候应用本身有健康监测机制。
ONBUILD触发器当存在 ONBUILD 关键字的镜像作为基础镜像的时候,当执行 FROM 完成之后,会执行 ONBUILD 的命令。但是不影响当前镜像,用处也不怎么大。
STOPSIGNAL发送信号量到宿主机STOPSIGNAL 指令设置将发送到容器的系统调用信号以退出。
SHELL指定执行脚本的 shell指定 RUN CMD ENTRYPOINT 执行命令的时候 使用的 shell。

案例

自定义 centos7 镜像

要求

  1. 默认登录路径为 /usr
  2. 可以使用 vim

实现步骤

dockerfile
# 使用 centos:7 作为父镜像
FROM centos:7

# 定义作者信息
# MAINTAINER itheima <itheima@itcast.cn>
LABEL version="1.0" maintainer="yourname@example.com"

# 执行安装 vim 命令
RUN yum install -y vim

# 定义默认的工作目录
WORKDIR /usr

# 定义容器启动执行的命令
CMD ["/bin/bash"]

通过 dockerfile 构建镜像:

bash
docker build -f Dockerfile -t centos-vim:1.0 .

自定义 springboot 镜像

需求

  • 定义 dockerfile,发布 springboot。

实现步骤

dockerfile
# 使用 java:8 作为父镜像
FROM java:8

# 定义作者信息
# MAINTAINER itheima <itheima@itcast.cn>
LABEL version="1.0" maintainer="yourname@example.com"

# 将 jar 包添加到容器
ADD springboot.jar app.jar

# 定义容器启动执行的命令
CMD ["java", "-jar", "app.jar"]

通过 dockerfile 构建镜像:

bash
docker build -f Dockerfile -t java-springboot:1.0 .